package com.example.commonback.service.impl;

import com.example.commonback.common.dto.CommonParmaDTO;
import com.example.commonback.common.result.Result;
import com.example.commonback.common.result.ResultEnum;
import com.example.commonback.common.utils.AssertUtils;
import com.example.commonback.common.utils.MapUtils;
import com.example.commonback.common.utils.PubUtils;
import com.example.commonback.mapper.CommonMapper;
import com.example.commonback.service.CommonService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 通用后端 业务逻辑层 具体实现
 *
 * @author Lv
 * @date 2022/8/10
 */
@SuppressWarnings("all")
@Slf4j
@Service
public class CommonServiceImpl implements CommonService {
    @Autowired
    private CommonMapper commonMapper;

    /**
     * 通用分页查询 无条件查询统计  有条件查询统计
     *
     * @param commonParmaDTO 通用后端参数传递对象
     * @return
     */
    @Override
    public Result<?> list(CommonParmaDTO commonParmaDTO) {
        // 判断是否有查询权限
        boolean allowList = commonParmaDTO.getTableEnum().isList();
        AssertUtils.isTrue(allowList, ResultEnum.NO_OPERATOR_AUTH);

        // 拼接条件
        String where = getWhere(commonParmaDTO);
        // 获取表名
        String tableName = commonParmaDTO.getTableEnum().name().toLowerCase();

        // 分页参数处理
        Integer page = commonParmaDTO.getPage();
        if (PubUtils.isNull(page) || page == 0) {
            commonParmaDTO.setPage(1);
        }
        Integer size = commonParmaDTO.getSize();
        if (PubUtils.isNull(size) || size == 0) {
            commonParmaDTO.setSize(10);
        }

        // 获取分页起点
        int start = (commonParmaDTO.getPage() - 1) * (commonParmaDTO.getSize());
        if (start < -1) {
            start = 0;
        }
        List<HashMap> list = null;
        Integer listCount = null;
        Map<String, Object> map = null;
        // 获取需要查询的字段
        String filed = this.getSelectFiled(tableName, commonParmaDTO.getTableEnum().getFiled());

        if (PubUtils.isEmpty(where)) {
            // 无条件查询
            listCount = commonMapper.listAllCount(tableName);
            // 当数据量为0时 无需继续访问数据库
            if (listCount == 0) {
                map = getResultMap(list, listCount, commonParmaDTO.getPage());
                return Result.success(map);
            }
            list = commonMapper.listAll(filed, tableName, start, commonParmaDTO.getSize());
        } else {
            // 带条件查询
            listCount = commonMapper.listCountByWhere(tableName, where);
            // 当数据量为0时 无需继续访问数据库
            if (listCount == 0) {
                map = getResultMap(list, listCount, commonParmaDTO.getPage());
                return Result.success(map);
            }
            list = commonMapper.listByWhere(filed, tableName, where, start, commonParmaDTO.getSize());
        }
        // 获取返回map数据
        map = getResultMap(list, listCount, commonParmaDTO.getPage());
        return Result.success(map);
    }

    /**
     * 获取返回map数据
     *
     * @param list      列表数据
     * @param listCount 数据数量
     * @param page      当前页
     * @return
     */
    private Map<String, Object> getResultMap(List<HashMap> list, Integer listCount, Integer page) {
        Map<String, Object> map = MapUtils.newHashMap();
        map.put("list", list);
        map.put("total", listCount);
        map.put("page", page);
        return map;
    }

    /**
     * 序列化编码字段
     */
    private static final String SERIAL_VERSION_UID = "serialVersionUID";

    /**
     * 符号 .
     */
    private static final String DOT = ".";

    /**
     * 实体包路径
     */
    @Value("${mybatis.type-aliases-package}")
    private String classPath;

    /**
     * 分隔符
     */
    private static final String SEP = ",";

    /**
     * 获取查询字段
     *
     * @param tableName 表名称
     * @param filed     不允许查询的字段
     * @return
     */
    @Override
    public String getSelectFiled(String tableName, String filed) {
        // 表名称转换为类名称
        String className = PubUtils.convertToCamelCase(tableName);

        // 反射方式获取实体包路径下的类字节码
        Class<?> aClass = null;
        try {
            aClass = Class.forName(classPath + DOT + className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            log.error("无法寻找到类 " + className);
        }

        // 获取并处理字段
        Field[] declaredFields = aClass.getDeclaredFields();

        // 将不允许被查询的字段转换为集合  方便过滤
        List<String> unSelectFiledList = PubUtils.strToList(filed, SEP, true, true);

        // 返回字段 返回形式 "a,b,c_d……"
        String selectFiled = Arrays.asList(declaredFields).stream()
                .filter(item -> !SERIAL_VERSION_UID.equalsIgnoreCase(item.getName()))
                .filter(item -> !unSelectFiledList.contains(PubUtils.toUnderScoreCase(item.getName())))
                .map(item -> PubUtils.toUnderScoreCase(item.getName()))
                .collect(Collectors.joining(","));

        // 健壮性处理
        AssertUtils.notEmpty(selectFiled, ResultEnum.PARAM_ERROR);
        return selectFiled;
    }

    /**
     * 数字正则值
     */
    private static final String NUMBER = "\\d*";

    /**
     * 精确查询
     */
    private static final String EQ = "eq";

    /**
     * 模糊查询
     */
    private static final String LIKE = "like";

    /**
     * 范围查询
     */
    private static final String BETWEEN = "between";

    /**
     * 拼接条件
     *
     * @param commonParmaDTO 通用后端参数传递对象
     * @return
     */
    private String getWhere(CommonParmaDTO commonParmaDTO) {
        StringBuffer where = new StringBuffer();
        if (PubUtils.isNotEmpty(commonParmaDTO.getWhere())) {
            commonParmaDTO.getWhere().stream().forEach(item -> {
                // 保证Sql正常执行 并防止Sql注入
                if (PubUtils.isNotEmpty(item.getFiled()) && PubUtils.isNotEmpty(item.getValue()) && !item.getFiled()
                        .equalsIgnoreCase(item.getValue())) {
                    // 处理字段
                    String tempFiled = parseValue(item.getFiled());
                    // 处理值
                    String tempValue = parseValue(item.getValue());

                    // 字段值不能存在数字 并且字段和值不相等
                    if (!tempFiled.matches(NUMBER) && !tempFiled.equalsIgnoreCase(tempValue)) {
                        // 精确查询 …… where 1 = 1 and 字段 = 值
                        if (EQ.equalsIgnoreCase(item.getType())) {
                            if (item.getStringType()) {
                                // 值为String 类型 字符拼接结果  字段 = '值'
                                where.append(" and ").append(tempFiled).append(" = ").append("\'").append(tempValue).append("\'");
                            } else {
                                // 值非String 类型 字符拼接结果  字段 = 值
                                where.append(" and ").append(tempFiled).append(" = ").append(tempValue);
                            }
                        }
                        // 模糊查询 …… where 1 = 1 and 字段 like '%值%'
                        if (LIKE.equalsIgnoreCase(item.getType())) {
                            where.append(" and ").append(tempFiled).append(" like \'%").append(tempValue).append("%\' ");
                        }
                        // 范围查询 …… where 1 = 1 and 字段 between 值1 and 值2
                        if (BETWEEN.equalsIgnoreCase(item.getType())) {
                            String[] values = tempValue.split(",");
                            where.append(" and ").append(tempFiled).append(" ").append(item.getType()).append(" ").append(values[0])
                                    .append(" and ").append(values[2]);
                        }
                    }
                }
            });
        }
        return where.toString();
    }

    /**
     * 防止Sql注入 替换敏感符号
     *
     * @param sql 待处理Sql字段或值
     * @return
     */
    private String parseValue(String sql) {
        if (PubUtils.isNotEmpty(sql)) {
            return sql.replaceAll(".*([';#%]+|(--)+).*", "");
        } else {
            return sql;
        }
    }

    /**
     * 添加操作标识符
     */
    private static final String ADD = "add";

    /**
     * 通用新增或修改 通过operate操作来判断
     *
     * @param commonParmaDTO 通用后端参数传递对象
     * @return
     */
    @Override
    public Result<?> saveOrUpdate(CommonParmaDTO commonParmaDTO) {
        // 获取操作
        String operate = commonParmaDTO.getOperate();
        // 获取表名称
        String tableName = commonParmaDTO.getTableEnum().name().toLowerCase();
        // 获取条件
        String where = getWhere(commonParmaDTO);
        if (ADD.equalsIgnoreCase(operate)) {
            if (PubUtils.isNotEmpty(where)) {
                return Result.failed(ResultEnum.DATA_ERROR, "新增操作不允许有条件");
            } else {
                // 插入数据
                return saveDate(commonParmaDTO, tableName);
            }
        } else {
            if (PubUtils.isEmpty(where)) {
                return Result.failed(ResultEnum.DATA_ERROR, "修改操作必须带有条件");
            } else {
                // 修改数据
                return updateDate(commonParmaDTO, tableName, where);
            }
        }
    }

    /**
     * 通用删除
     *
     * @param commonParmaDTO 通用后端参数传递对象
     * @return
     */
    @Override
    public Result<?> delete(CommonParmaDTO commonParmaDTO) {
        // 获取条件
        String where = getWhere(commonParmaDTO);
        // 获取表名
        String tableName = commonParmaDTO.getTableEnum().name().toLowerCase();
        // 断言具有删除权限
        AssertUtils.isTrue(commonParmaDTO.getTableEnum().isDelete(), ResultEnum.NO_OPERATOR_AUTH);
        // 条件校验
        if (PubUtils.isEmpty(where)) {
            Result.failed(ResultEnum.DATA_ERROR, "删除条件不合法");
        }
        // 操作删除
        int count = commonMapper.delete(tableName, where);
        return Result.success(count);
    }

    /**
     * 获取详情
     *
     * @param commonParmaDTO 通用后端参数传递对象
     * @return
     */
    @Override
    public Result<?> get(CommonParmaDTO commonParmaDTO) {
        // 获取表名
        String tableName = commonParmaDTO.getTableEnum().name().toLowerCase();
        // 断言具有详情权限
        AssertUtils.isTrue(commonParmaDTO.getTableEnum().isGet(), ResultEnum.NO_OPERATOR_AUTH);
        String where = getWhere(commonParmaDTO);
        if (PubUtils.isEmpty(where)) {
            return Result.failed(ResultEnum.DATA_ERROR, "获取详情必须带条件");
        }

        // 获取允许查询的字段
        String filed = this.getSelectFiled(tableName, commonParmaDTO.getTableEnum().getFiled());

        // 获取详情 查询部分字段
        HashMap map = commonMapper.getPartFiled(filed, tableName, where);
        Map<String, Object> dataMap = MapUtils.newHashMap();
        dataMap.put("data", map);
        return Result.success(dataMap);
    }

    /**
     * 插入数据
     *
     * @param commonParmaDTO 通用后端查询参数传递对象
     * @param tableName      表名称
     * @return
     */
    private Result<?> saveDate(CommonParmaDTO commonParmaDTO, String tableName) {
        // 断言具有查询权限
        AssertUtils.isTrue(commonParmaDTO.getTableEnum().isAdd(), ResultEnum.NO_OPERATOR_AUTH);
        // 获取字段以及值
        String[] sql = getInsertSql(commonParmaDTO);
        // 断言SQL语句不为空
        AssertUtils.notEmpty(sql, ResultEnum.DATA_ERROR);

        // 数据持久化
        int count = commonMapper.insert(tableName, sql[0], sql[1]);
        return Result.success(count);
    }

    /**
     * 拼接插入的SQL
     * ['字段1'，'字段2'，……]
     * [' 值1 '，' 值2 ', ……]
     *
     * @param commonParmaDTO 通用后端查询参数传递对象
     * @return
     */
    private String[] getInsertSql(CommonParmaDTO commonParmaDTO) {
        StringBuffer fileds = new StringBuffer();
        StringBuffer values = new StringBuffer();
        AtomicInteger count = new AtomicInteger();
        if (commonParmaDTO.getSets() != null) {
            commonParmaDTO.getSets().stream().forEach(item -> {
                if (PubUtils.isEmpty(item.getValue())) {
                    // 计数
                    count.incrementAndGet();
                } else {
                    // 解析字段
                    String tempF = parseValue(item.getFiled());
                    // 解析值
                    String tempV = parseValue(item.getValue());
                    // 正则校验 防止Sql注入
                    if (!tempF.matches(NUMBER) && !tempF.equalsIgnoreCase(tempV)) {
                        if (fileds.length() > 0) {
                            fileds.append(",");
                            values.append(",");
                        }
                        fileds.append(tempF);
                        values.append("\'").append(tempV).append("\'");
                    }
                }
            });
        }
        if (count.get() > 0) {
            return null;
        }
        return new String[]{fileds.toString(), values.toString()};
    }

    /**
     * 修改操作
     *
     * @param commonParmaDTO 通用后端参数传递对象
     * @param tableName      表名称
     * @param where          条件
     * @return
     */
    private Result<?> updateDate(CommonParmaDTO commonParmaDTO, String tableName, String where) {
        // 断言具有修改权限
        AssertUtils.isTrue(commonParmaDTO.getTableEnum().isEdit(), ResultEnum.NO_OPERATOR_AUTH);
        // 获取修改字段以及值
        String sets = getSets(commonParmaDTO);
        if (PubUtils.isEmpty(sets)) {
            return Result.failed(ResultEnum.DATA_ERROR, "修改的参数值不能为空");
        }
        // 修改数据
        int count = commonMapper.update(tableName, where, sets);
        return Result.success(count);
    }

    /**
     * 拼接修改字段以及值
     *
     * @param commonParmaDTO 通用后端参数传递对象
     * @return
     */
    private String getSets(CommonParmaDTO commonParmaDTO) {
        StringBuffer sets = new StringBuffer();
        AtomicInteger count = new AtomicInteger();
        if (commonParmaDTO.getSets() != null) {
            commonParmaDTO.getSets().stream().forEach(item -> {
                if (PubUtils.isEmpty(item.getValue())) {
                    count.incrementAndGet();
                } else {
                    String tempF = parseValue(item.getFiled());
                    String tempV = parseValue(item.getValue());
                    if (!tempF.matches(NUMBER) && !tempF.equalsIgnoreCase(tempV)) {
                        if (sets.length() > 0) {
                            sets.append(",");
                        }
                        sets.append(tempF).append("=\'").append(tempV).append("\'");
                    }
                }

            });
        }
        if (count.get() > 0) {
            return null;
        }
        return sets.toString();
    }
}
